#include <mpi.h>

#include "GRASP.h"
#include "Kruskal.h"
#include "FileSTS.h"

#define MAX_ITER 120

#define TAG_CONS_SOL 98
#define TAG_CONS_AVAL 99
#define TAG_VNS_SOL 100
#define TAG_VNS_AVAL 101

#define RECV_CONS_SOL 102
#define RECV_CONS_AVAL 103

#define TAG_FINALIZE 104

double startwtime = 0.0, endwtime;
int melhorIter = 0;

struct SolucaoStruct {
    double aval;
    char data[1000];
};

MPI_Datatype SolucaoType;
MPI_Datatype type[2] = {MPI_DOUBLE, MPI_CHAR};

int main(int argc, char** argv) {
    if (argc == 4) {
        int size, rank;

        MPI_Init(&argc, &argv);
        MPI_Comm_size(MPI_COMM_WORLD, &size);
        MPI_Comm_rank(MPI_COMM_WORLD, &rank);

        srand(time(NULL));

        //Inicializando os dados de arquivo
        Solucao::loadEntrada(argv[1]);
        double alpha = atof(argv[2]);
        GRASP g(Solucao::tamanho, alpha);

        int blocken[2] = {1, 1000};
        MPI_Aint disp[2] = {0, sizeof (double)};

        //Definindo o datatype.
        MPI_Type_struct(2, blocken, disp, type, &SolucaoType);
        MPI_Type_commit(&SolucaoType);

        //Pegando o tempo inicial
        if (rank == 0)
            startwtime = MPI_Wtime();

        SolucaoGRASPSTS *sbest = NULL;
        char *data = NULL;
        double aval;
        if (rank != 0) {
            MPI_Status status;
            int finish = 0;
            int cont = 0;

            //printf(">>>>> PROCESSO %d INICIOU \n", rank);

            while (true) {

                cont++;
                //Fase de Construcao e Busca Local.
                SolucaoGRASPSTS *sol = g.construcao();
                SolucaoGRASPSTS *sbl = g.descida(sol);
                delete sol;

                //data = sbl->print();
                SolucaoStruct ss;
                strcpy(ss.data, sbl->print());
                ss.aval = sbl->avaliacao;
                //Enviando a sua melhor solucao para o servidor.
                MPI_Send(&ss, 1, SolucaoType, 0, TAG_CONS_SOL, MPI_COMM_WORLD);

                SolucaoStruct sr;
                //Recebendo a melhor solucao do servidor. 
                MPI_Recv(&sr, 1, SolucaoType, 0, RECV_CONS_SOL, MPI_COMM_WORLD, &status);

                delete sbl;
                sbl = new SolucaoGRASPSTS(sr.data);
                sbl->avaliacao = sr.aval;

                //Executando o VNS.
                SolucaoGRASPSTS *svns = g.ILS(sbl);

                SolucaoStruct ss2;
                strcpy(ss2.data, svns->print());
                ss2.aval = svns->avaliacao;
                //Enviando a sua melhor solucao para o servidor.
                MPI_Send(&ss2, 1, SolucaoType, 0, TAG_VNS_SOL, MPI_COMM_WORLD);
                delete svns;

                MPI_Recv(&finish, 1, MPI_INT, 0, TAG_FINALIZE, MPI_COMM_WORLD, &status);

                if (finish == 1) {
                    //printf("PROCESSO %d RECEBEU COMANDO DE FINALIZACAO\n", rank);
                    break;
                }


            }

            printf(">>>>> PROCESSO %d Finalizou e fez %d iteracoes \n", rank, cont);
        } else {
            int cont = 0;
            int cont_process_dead = 0;

            while (true) {
                MPI_Status status;
                SolucaoStruct ss;
                int source;
                int finish = 0;

                MPI_Recv(&ss, 1, SolucaoType, MPI_ANY_SOURCE, MPI_ANY_TAG, MPI_COMM_WORLD,
                        &status);

                switch (status.MPI_TAG) {
                    case TAG_CONS_SOL:

                        //printf("CONSTRUCAO Iteracao corrente %d  recebida de %d\n", cont, source);

                        source = status.MPI_SOURCE;
                        if (sbest == NULL) {
                            sbest = new SolucaoGRASPSTS(ss.data);
                            sbest->avaliacao = ss.aval;
                            melhorIter = cont;
                        } else {
                            if (ss.aval < sbest->avaliacao) {
                                sbest = new SolucaoGRASPSTS(ss.data);
                                sbest->avaliacao = ss.aval;
                                melhorIter = cont;
                                //printf("CONS %s\n", ss.data);
                                //printf("CONS %f\n", ss.aval);
                            }
                        }
                        SolucaoStruct sr;
                        strcpy(sr.data, sbest->print());
                        sr.aval = sbest->avaliacao;

                        //printf("ANTES CONSTRUCAO Iteracao corrente %d  recebida de %d\n", cont, source);
                        MPI_Send(&sr, 1, SolucaoType, source, RECV_CONS_SOL,
                                MPI_COMM_WORLD);
                        //	printf("DEPOIS CONSTRUCAO Iteracao corrente %d  recebida de %d\n", cont, source);
                        break;
                    case TAG_VNS_SOL:
                        if (ss.aval < sbest->avaliacao) {
                            sbest = new SolucaoGRASPSTS(ss.data);
                            sbest->avaliacao = ss.aval;
                            melhorIter = cont;
                            //printf("VNS %s\n", ss.data);
                            //printf("VNS %f\n", ss.aval);
                        }



                        cont++;
                        finish = cont > (MAX_ITER - (size - 1)) ? 1 : 0;

                        MPI_Send(&finish, 1, MPI_INT, status.MPI_SOURCE, TAG_FINALIZE,
                                MPI_COMM_WORLD);




                        break;
                    default:
                        puts("Erro no sincronismo das mensagens.");
                        exit(0);
                        break;
                }


                if (finish == 1) {
                    cont_process_dead++;
                    if (cont_process_dead == (size - 1)) {
                        break;
                    }
                }


            }
        }

        if (rank == 0) {
            endwtime = MPI_Wtime();
            FileSTS::printInstancia(argv[3], size, alpha, sbest->tamanho, sbest->numClient, melhorIter,
                    sbest->avaliacao, (int) ((endwtime - startwtime) * 1000));
            printf("Final: %s F: %f Tempo: %f\n", sbest->print(), sbest->avaliacao,
                    endwtime - startwtime);
        }

    } else {
        puts("<usage: grasp nameFile alpha estrategia>");
    }
    MPI_Finalize();
    return 0;
}

